package com.qianxun.api;

import com.google.gson.JsonElement;
import com.google.gson.JsonParser;
import com.qianxun.api.anno.ApiInfo;
import com.qianxun.api.anno.Param;
import com.qianxun.api.anno.RequestMethod;
import com.qianxun.api.bo.Result;
import com.qianxun.api.error.NoServiceException;
import com.qianxun.api.error.ResultAdaptException;
import com.qianxun.framework.util.ErrorUtil;
import com.qianxun.framework.util.GsonFormatBuilder;
import com.qianxun.framework.util.ObjectUtil;
import com.qianxun.framework.util.expression.ExpressionUtil;
import com.qianxun.framework.util.http.HttpConnection;
import com.qianxun.framework.util.http.HttpResponse;
import com.qianxun.framework.util.http.HttpUtil;
import com.qianxun.framework.util.http.UploadFileInfo;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.*;

/**
 * api工具对类
 *
 * @author: huwei
 * @date: 2019/9/18 15:42
 * @version: 1.0.0
 */
public class ApiLoader {
    /**
     * api配置参数
     */
    private ApiConfiguration apiConfiguration;
    /**
     * 全局共享参数生成器
     */
    private AdditionalParameterGenerator shareParameterGenerator;

    private ApiLoader(ApiConfiguration apiConfiguration ,AdditionalParameterGenerator shareParameterGenerator) {
        this.apiConfiguration = apiConfiguration;
        this.shareParameterGenerator = shareParameterGenerator;
    }

    /**
     * 构建Api处理工具
     *
     * @param host      接口请求服务器地址
     * @param appId     应用的app id
     * @param secretKey 应用的加密key
     * @return
     */
    public static ApiLoader load(String host, String appId, String secretKey) {
        return load(new ApiConfiguration(host, appId, secretKey));
    }

    /**
     * 构建api处理工具
     *
     * @param configuration
     * @return
     */
    public static ApiLoader load(ApiConfiguration configuration) {
        return load(configuration ,null);
    }

    /**
     * 构建api处理工具
     *
     * @param configuration
     * @param shareParameterGenerator
     * @return
     */
    public static ApiLoader load(ApiConfiguration configuration ,AdditionalParameterGenerator shareParameterGenerator){
        return new ApiLoader(configuration ,shareParameterGenerator);
    }

    public ApiBuilder build() {
        return build((String)null);
    }

    public ApiBuilder build(String originalIp) {
       return build(originalIp ,null ,null);
    }

    public ApiBuilder build(AdditionalParameterGenerator additionalParameterGenerator ){
        return build(null ,additionalParameterGenerator ,null);
    }

    public ApiBuilder builder(ResultAdapter resultAdapter){
        return build(null ,null ,resultAdapter);
    }

    public ApiBuilder build(String originalIp , AdditionalParameterGenerator additionalParameterGenerator ,ResultAdapter resultAdapter){
        return new ApiBuilder(apiConfiguration).setOriginalIp(originalIp).setAdditionalParameterGenerator(additionalParameterGenerator)
                .setShareParameterGenerator(shareParameterGenerator);
    }

    /**
     * api 建造工具
     */
    public static class ApiBuilder {
        private ApiConfiguration apiConfiguration;

        private String originalIp = "";
        /**
         * 附加参数生成器
         */
        private AdditionalParameterGenerator additionalParameterGenerator;
        /**
         * 全局参数生成器
         */
        private AdditionalParameterGenerator shareParameterGenerator;
        /**
         * 结果适配对象
         */
        private ResultAdapter resultAdapter;

        private ApiBuilder(ApiConfiguration apiConfiguration) {
            this.apiConfiguration = apiConfiguration;
        }
        ApiBuilder setOriginalIp(String originalIp){
            this.originalIp = ObjectUtil.isBlank(originalIp) ? "" : originalIp;
            return this;
        }
        ApiBuilder setAdditionalParameterGenerator(AdditionalParameterGenerator additionalParameterGenerator){
            this.additionalParameterGenerator = additionalParameterGenerator;
            return this;
        }
        ApiBuilder setShareParameterGenerator(AdditionalParameterGenerator shareParameterGenerator){
            this.shareParameterGenerator = shareParameterGenerator;
            return this;
        }
        ApiBuilder setResultAdapter(ResultAdapter resultAdapter){
            this.resultAdapter = resultAdapter;
            return this;
        }

        /**
         * 实例指定api类对象的对象
         *
         * @param apiClass api接口类对象
         * @param <T>      api接口实例
         * @return
         */
        public <T> T instanceApi(Class<? extends T> apiClass) {
            return instanceApi(apiClass, new ApiRequestMonitor() {
            });
        }

        /**
         * 实例指定api类对象的对象
         *
         * @param apiClass          api接口类对象
         * @param apiRequestMonitor api请求监视器
         * @param <T>               api接口实例
         * @return
         */
        public <T> T instanceApi(Class<? extends T> apiClass, ApiRequestMonitor apiRequestMonitor) {
            return (T) new ApiDynamicProxy(apiClass, apiRequestMonitor ,this).getProxyObject();
        }
    }

    /**
     * Api接口动态代理类，代理接口方法，访问指定url获取数据信息
     */
    private static class ApiDynamicProxy<T> implements MethodInterceptor {
        /**
         * api接口类对象
         */
        private Class<? extends T> apiClass;
        /**
         * api请求监视器
         */
        private ApiRequestMonitor monitor;

        private ApiBuilder apiBuilder;

        ApiDynamicProxy(Class<? extends T> apiClass, ApiRequestMonitor monitor ,ApiBuilder apiBuilder) {
            this.apiClass = apiClass;
            this.monitor = monitor;
            this.apiBuilder = apiBuilder;
        }

        /**
         * 获取代理对象
         *
         * @return
         */
        public T getProxyObject() {
            Enhancer enhancer = new Enhancer();
            enhancer.setCallback(this);
            enhancer.setSuperclass(apiClass);
            return (T) enhancer.create();
        }

        @Override
        public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
            ApiInfo apiInfo = method.getAnnotation(ApiInfo.class);
            if (ObjectUtil.isNull(apiInfo)) {
                throw new NoServiceException("无法请求服务：".concat(apiClass.getName()).concat("接口的").concat(method.toString()).concat("没有定义Api信息"));
            }

            Map<String, String> requestParameterMap = parseMethodParameter(method, objects);
            //文件参数
            Collection<UploadFileInfo> uploadFileInfoList = null;
            if (apiInfo.uploadFile() || apiInfo.batchUploadFile()) {
                //删除请求参数中的上传文件信息
                requestParameterMap.remove(apiInfo.fileInfoParameter());
                uploadFileInfoList = parseUploadInfo(method, apiInfo, objects);
            }

            String[] url = parseUrl(apiInfo, this.apiBuilder.apiConfiguration.getHost(), requestParameterMap);
            //前置回调
            monitor.before(apiClass, method.getName(), url, requestParameterMap);
            try {
                HttpResponse response = request(apiClass, method, url, requestParameterMap, uploadFileInfoList);
                Object res = adaptResult(apiClass, method, response);
                //后置回调
                monitor.after(apiClass, method.getName(), url, requestParameterMap, res);
                return res;
            } catch (Throwable cause) {
                //异常回调
                monitor.error(apiClass, method.getName(), url, requestParameterMap, cause);
                throw cause;
            }
        }

        private Object adaptResult(Class<? extends T> apiClass, Method method, HttpResponse response) {
            if (this.apiBuilder.resultAdapter == null) {
                return new ResultAdapter() {
                }.adapt(response, method.getReturnType());
            } else {
                return this.apiBuilder.resultAdapter.adapt(response, method.getReturnType());
            }
        }

        private Collection<UploadFileInfo> parseUploadInfo(Method method, ApiInfo apiInfo, Object[] objects) throws Throwable {
            Annotation[][] annotations = method.getParameterAnnotations();
            for (int i = 0; i < annotations.length; i++) {
                for (Annotation annotation : annotations[i]) {
                    if (annotation instanceof Param) {
                        Object o = objects[i];
                        if (o != null && apiInfo.fileInfoParameter().equals(((Param) annotation).value())) {
                            if (apiInfo.batchUploadFile()) {
                                return (Collection<UploadFileInfo>) o;
                            }
                            return Arrays.asList((UploadFileInfo) o);
                        }
                    }
                }
            }
            throw new Throwable("上传文件信息不能为空");
        }

        /**
         * 请求url信息
         *
         * @param apiClass            指定接口类对象
         * @param method              指定方法
         * @param urls                url请求地址列表
         * @param requestParameterMap 请求参数列表
         * @return 当指定方法无指定uri信息，或请求url错误时，将抛出异常
         * @throws Throwable
         */
        private HttpResponse request(Class<?> apiClass, Method method, String[] urls, Map<String, String> requestParameterMap, Collection<UploadFileInfo> uploadFileInfoCollection) throws Throwable {
            ApiInfo apiInfo = method.getAnnotation(ApiInfo.class);
            Map<String, Throwable> errorMap = new HashMap<>(urls.length);
            for (String url : urls) {
                HttpConnection httpConnection = HttpUtil.connection(url).setParameter(requestParameterMap);

                //设置附加参数
                setAdditionalParameter(httpConnection ,this.apiBuilder.apiConfiguration ,this.apiBuilder.shareParameterGenerator);
                setAdditionalParameter(httpConnection ,this.apiBuilder.apiConfiguration ,this.apiBuilder.additionalParameterGenerator);
                setAdditionalParameter(httpConnection ,this.apiBuilder.apiConfiguration ,new AdditionalParameterGenerator() {
                    @Override
                    public AdditionalParameter[] headerParameter(ApiConfiguration apiConfiguration) {
                        return ObjectUtil.isBlank(apiBuilder.originalIp) ? new AdditionalParameter[0]
                                : new AdditionalParameter[]{new AdditionalParameter("originalIp" ,apiBuilder.originalIp)};
                    }
                });

                try {
                    if (apiInfo.uploadFile() || apiInfo.batchUploadFile()) {
                        List<UploadFileInfo> list = new ArrayList<>();

                        list.addAll(uploadFileInfoCollection);
                        return httpConnection.setConnectionTimeout(50 * 1000).upload(list);
                    } else if (apiInfo.method() == RequestMethod.GET) {
                        return httpConnection.get();
                    } else {
                        return httpConnection.post();
                    }
                } catch (Throwable e) {
                    //暂时不做处理，继续请求后续url目标，若所有url都错误，则在最后跑出异常
                    errorMap.put(url, e);
                }
            }
            StringBuffer errorBuf = new StringBuffer();
            errorMap.forEach((uri, throwable) -> {
                errorBuf.append(apiClass.getName()).append(" 接口的 ").append(method).append(" 方法的uri请求地址错误： ").append(uri).append("\r\n");
                errorBuf.append(ErrorUtil.getStackTrace(throwable));
            });
            throw new Throwable(errorBuf.toString());
        }

        /**
         * 解析方法参数为请求参数
         *
         * @param method  方法
         * @param objects 参数列表
         * @return
         */
        private Map<String, String> parseMethodParameter(Method method, Object[] objects) {
            Map<String, String> parameterMap = new HashMap<>(objects.length);
            Annotation[][] annotations = method.getParameterAnnotations();
            for (int i = 0; i < annotations.length; i++) {
                for (Annotation annotation : annotations[i]) {
                    if (annotation instanceof Param) {
                        Object o = objects[i];
                        if (Objects.nonNull(o)) {
                            parameterMap.put(((Param) annotation).value(), o.toString());
                        }
                        break;
                    }
                }
            }
            return parameterMap;
        }

        /**
         * 辅助参数设定
         * @param httpConnection
         * @param apiConfiguration
         * @param additionalParameterGenerator
         */
        private void setAdditionalParameter(HttpConnection httpConnection ,ApiConfiguration apiConfiguration ,AdditionalParameterGenerator additionalParameterGenerator){
            if(additionalParameterGenerator != null){
                AdditionalParameterGenerator.AdditionalParameter[] headerParameters = additionalParameterGenerator.headerParameter(apiConfiguration);
                if(headerParameters != null && headerParameters.length > 0){
                    for(int i = 0 ;i < headerParameters.length ;++i){
                        httpConnection.setHeader(headerParameters[i].getParameterName() ,headerParameters[i].getParameterValue());
                    }
                }
                AdditionalParameterGenerator.AdditionalParameter[] additionalParameters = additionalParameterGenerator.bodyParameter(apiConfiguration);
                if(additionalParameters != null && additionalParameters.length > 0){
                    for(int i = 0 ;i < additionalParameters.length ;++i){
                        httpConnection.setHeader(additionalParameters[i].getParameterName() ,additionalParameters[i].getParameterValue());
                    }
                }
            }
        }

        /**
         * 解析给定apiInfo的所有url地址
         *
         * @param apiInfo api信息
         * @return
         */
        private String[] parseUrl(ApiInfo apiInfo, String host, Map<String, String> requestParameterMap) {
            String[] urls = new String[apiInfo.uri().length];
            String[] uris = apiInfo.uri();
            //表达式工具
            ExpressionUtil expressionUtil = ExpressionUtil.build("{", "}");
            //表达式环境设置
            Map<String, Object> evn = new HashMap<>(requestParameterMap.size());
            requestParameterMap.forEach((k, v) -> {
                evn.put(k, v);
            });
            for (int i = 0; i < uris.length; ++i) {
                urls[i] = host + expressionUtil.parse(uris[i], evn);
            }
            return urls;
        }
    }
}
